package com.heima.schedule.service;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.redis.CacheService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.dtos.TaskDto;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.pojos.schedule.Taskinfo;
import com.heima.model.common.pojos.schedule.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.xml.ws.Response;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
public class TaskServiceImpl implements TaskService {
    @Autowired
    private TaskinfoMapper taskinfoMapper;

    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;

    @Autowired
    private CacheService cacheService;


    /**
     * 添加任务
     *
     * @param taskDto
     * @return
     */
    @Override
    public ResponseResult addTask(TaskDto taskDto) {
        //1. 先将任务添加到数据库
        addTaskToDB(taskDto);

        //2.再根据任务的时间来决定要不要将任务添加到Redis，以及是添加到List还是ZSet
        addTaskToRedis(taskDto);
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 拉取任务
     *
     * @param taskType
     * @param priority
     * @return
     */
    @Override
    public ResponseResult pullTask(Integer taskType, Integer priority) {
        //1. 根据任务类型和优先级去redis的List队列中取出对应key的任务数据
        String key = "topic_" + taskType + "_" + priority;
        //取出队列待处理的下一个任务，先进先出
        String taskDtoJSON = cacheService.lRightPop(key);

        //如果任务不为空
        if (StringUtils.isNotEmpty(taskDtoJSON)) {
            //2.1 将其当前任务对应的DB中的Taskinfo表中记录删除掉
            //将JSON转为dto
            TaskDto taskDto = JSON.parseObject(taskDtoJSON, TaskDto.class);
            //删除taskinfo表中的记录
            taskinfoMapper.deleteById(taskDto.getTaskId());

            //2.2 将当前任务对应的DB中的TaskinfoLogs表中状态修改为：1：已执行
            TaskinfoLogs logs = new TaskinfoLogs();
            //设置任务id
            logs.setTaskId(taskDto.getTaskId());
            //设置状态为已执行
            logs.setStatus(1);
            //更新logs表
            taskinfoLogsMapper.updateById(logs);

            //3. 将当前任务信息返回
            //如果拉取任务成功
            return ResponseResult.okResult(taskDtoJSON);
        }else {
            //如果拉取任务失败
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
    }

    //todo：1.定义一个定时任务：定时刷新ZSet数据到List中，任务执行频次：每1分钟执行一次
    @Scheduled(cron = "0 0/1 * * * ?")
    @PostConstruct
    public void refreshZSetToList() {
        //查询所有的ZSet的key
        Set<String> zsetKeys = cacheService.scan("future_*");

        if (zsetKeys != null && zsetKeys.size() > 0) {
            log.info("-------------定时刷新ZSet数据到List中-------------");
            //筛选出ZSet中任务执行时间 <= 当前系统时间的任务数据
            for (String zsetKey : zsetKeys) {
                Set<String> taskDtos = cacheService.zRangeByScore(zsetKey, 0, System.currentTimeMillis());

                //将这批到期的任务由ZSet集合转移到List集合
                String[] split = zsetKey.split("future_");
                String listKey = "topic_" + split[1];

                //将已经到期的这批任务：taskDtos从zsetKey对应的集合中移除，并且添加到listKey对应的集合中
                cacheService.refreshWithPipeline(zsetKey, listKey, taskDtos);
            }
        }
    }

    //todo：2.定义一个定时任务，定时刷新DB数据到Redis中，任务执行频次：每5分钟执行一次
    @Scheduled(cron = "0 0/5 * * * ?")
    @PostConstruct
    public void refreshDBToRedis() {
        //查询出数据库中执行时间 <= 预设时间 的任务数据都查出来
        Date futureTime = new Date(System.currentTimeMillis() + (1000L * 60 * 5));
        List<Taskinfo> taskinfoList = taskinfoMapper.selectList(Wrappers.<Taskinfo>lambdaQuery().le(Taskinfo::getExecuteTime, futureTime));

        if (taskinfoList != null && taskinfoList.size() > 0) {
            log.info("-------------定时刷新DB数据到Redis中-------------");
            //遍历集合：拿到每一个任务
            for (Taskinfo taskinfo : taskinfoList) {
                //添加任务到Redis
                TaskDto dto = new TaskDto();
                BeanUtils.copyProperties(taskinfo, dto);
                dto.setExecuteTime(taskinfo.getExecuteTime().getTime());
                addTaskToRedis(dto);
            }
        }
    }

    //添加任务到redis
    private void addTaskToRedis(TaskDto taskDto) {
        //1. 拿到几个时间
        //拿到任务的执行时间
        long executeTime = taskDto.getExecuteTime();
        //拿到当前系统时间
        long now = System.currentTimeMillis();
        //拿到预加载时间：当前系统时间 + 5分钟
        long futureTime = now + (1000L * 60 * 5);

        //2. 任务执行时间 <= 当前系统时间：将任务添加到List
        if (executeTime <= now) {
            //将dto转为JSON字符串
            cacheService.lLeftPush("topic_" + taskDto.getTaskType() + "_" + taskDto.getPriority(), JSON.toJSONString(taskDto));
            //添加到列表
        } else if (executeTime <= futureTime) {
            //3. 任务执行时间 > 当前系统时间 && <= 预加载时间：将任务添加到ZSet中
            //添加到有序集合
            //executeTime作为分数用来排序
            cacheService.zAdd("future_" + taskDto.getTaskType() + "_" + taskDto.getPriority(), JSON.toJSONString(taskDto), executeTime);
        }

    }

    //添加任务到数据库
    private void addTaskToDB(TaskDto taskDto) {
        //先保存任务数据到taskinfo
        Taskinfo taskinfo = new Taskinfo();
        //从dto拷贝数据
        BeanUtils.copyProperties(taskDto,taskinfo);
        //设置执行时间(将long转为date，数据都为毫秒时间戳则可以使用new Date进行转换)
        taskinfo.setExecuteTime(new Date(taskDto.getExecuteTime()));
        //插入数据到taskinfo表
        taskinfoMapper.insert(taskinfo);
        //将任务id回填到dto上，便于后期任务删除
        taskDto.setTaskId(taskinfo.getTaskId());

        //再保存任务数据到taskinfo_logs
        TaskinfoLogs logs = new TaskinfoLogs();
        //从taskinfo拷贝数据
        BeanUtils.copyProperties(taskinfo,logs);
        //初始版本号为1
        logs.setVersion(1);
        // 修改状态为初始化：0-初始化 1-已执行 2-已取消
        logs.setStatus(0);
        //添加进logs表
        taskinfoLogsMapper.insert(logs);
    }
}
